// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Duan Lian (duanlian01@baidu.com)
//
// Description: OTA Manager

#include "baidu_iot_ota_manager.h"
#include <assert.h>
#include "duer_log.h"
#include "baidu_json.h"
#include "IOtaUpdater.h"
#include "heap_monitor.h"
#ifdef TARGET_UNO_91H
#include "rda_ota_updater.h"
#endif
#include "baidu_iot_ota_log.h"
#include "baidu_iot_ota_url.h"
#include "baidu_ca_scheduler.h"
#include "baidu_iot_ota_decompresser.h"
#include "baidu_iot_ota_coap_downloader.h"
#include "baidu_iot_ota_http_downloader.h"
#include "baidu_iot_ota_unpack_tool.h"

namespace duer {

struct downloader_cb_ctx {
    unsigned int nOffset;
    unsigned int nTotalSize;
    OTAManager* p_ota_manager;
    double dbPrevPercent;
};

static const unsigned int WRITE_STACK_SIZE    = 1024 * 8;
static const unsigned int DOWNLOAD_STACK_SIZE = 1024 * 6;

OTAManager OTAManager::_s_manager;
OTAManager* OTAManager::instance() {
    return &_s_manager;
}

OTAManager::OTAManager()
    : _m_shared_buffer(SHARED_BUFFER_SIZE),
      _m_b_idle(true),
      _m_p_update_command(NULL),
      _m_p_thread(NULL),
      _m_p_restart_thread(NULL),
      _m_lock(),
      _m_p_install_complete(NULL) {
}

OTAManager::~OTAManager() {
    if (_m_p_thread) {
        delete _m_p_thread;
        _m_p_thread = NULL;
    }
}

// 0 will check, 1 will not check
int OTAManager::check_updates(const char* command) {
    int n_check = 0;

    OTA_FUNC_ENTER();
    DUER_LOGD("Parameter:\ncommand:%s\n", command ? command : "null pointer");

    OTAServerUpdateCommand parse_command;
    bool command_is_valid = ((command != 0)
                             && (0 == parse_update_command(command, parse_command)));

    if (!command_is_valid) {
        DUER_LOGE("Update command is invalid, quit");
        n_check = 1;
    }

    if (n_check == 0 && !_m_lock.trylock()) {
        DUER_LOGE("Cannot acquire lock, quit");
        n_check = 2;
    }

    if (n_check == 0) {
        do {
            DUER_LOGD("OTA check _m_b_idle:%s", _m_b_idle ? "yes" : "no");

            if (_m_b_idle) {
                if (_m_p_thread) {
                    delete _m_p_thread;
                    _m_p_thread = NULL;
                }

                if (_m_p_update_command) {
                    FREE(_m_p_update_command);
                    _m_p_update_command = NULL;
                }

                if (_m_p_reporter) {
                    delete _m_p_reporter;
                    _m_p_reporter = NULL;
                }

                int update_command_len = strlen(command) + 1;
                // Start a new thread to check updates
                _m_p_update_command = (char*)MALLOC(update_command_len, OTA);

                if (_m_p_update_command != NULL) {
                    DUER_LOGD("Copy command to %p", _m_p_update_command);
                    memmove(_m_p_update_command, command, update_command_len);
                } else {
                    DUER_LOGE("Copy command error:malloc fails");
                    n_check = 3;

                    break;
                }

                _m_b_idle = false;
                _m_p_thread = NEW(OTA) rtos::Thread(osPriorityHigh, DOWNLOAD_STACK_SIZE);
                DUER_LOGI("Starts new thread run_downloader 0x%08x", _m_p_thread->gettid());
                _m_p_thread->start(this, &OTAManager::run_downloader);
            } else {
                DUER_LOGE("Another upgrade process is ongoing");
                n_check = 4;
            }
        } while (0);

        _m_lock.unlock();
    }

    DUER_LOGD("n_check:%d\n", n_check);

    if (n_check != 0) {
        OTAReporter reporter(parse_command.transaction);
        reporter.report_event(OTAReporter::OTA_EVENT_REJECT);
    }

    OTA_FUNC_LEAVE();

    return n_check;
}

void OTAManager::run_downloader() {
    OTA_FUNC_ENTER();
    // Contact ota server first to check if there are available updates
    OTAServerUpdateCommand command;
    bool command_is_valid = (_m_p_update_command
                             && 0 == parse_update_command(_m_p_update_command, command));

    if (!command_is_valid) {
        DUER_LOGE("Command invalid, should not come here!!!");

        return;
        // Contact_ota_server();
    }

    _m_p_reporter = NEW(OTA) OTAReporter(command.transaction);
    bool update = true;
#ifdef TARGET_UNO_91H
    update = false;
    struct os_info info;

    if (0 == _m_scanner.scan()) {
        _m_scanner.get_os_info(info);

        /*
         * Here check the old_version in command is equal the current version
         * and the current version is not equal the version in command.
         * if support an older version to do the update decided by the cloud server.
         */
        if ((0 == strcmp(command.old_version, info.version))
                && (0 != strcmp(command.version, info.version))
           ) {
            update = true;
        }
    }

#endif

    if (update) {
        DUER_LOGI("Device can perform update, now start");
        apply_updates(command);
    } else {
        DUER_LOGE("The version in command is not acceptable");
        DUER_LOGE("Command.old_version:%s, command.version:%s, info.version:%s!!!",
                    command.old_version, command.version, info.version);
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_REJECT);
    }

    if (_m_p_update_command) {
        FREE(_m_p_update_command);
        _m_p_update_command = NULL;
    }

    if (_m_p_reporter) {
        delete _m_p_reporter;
        _m_p_reporter = NULL;
    }

    _m_lock.lock();
    _m_b_idle = true;
    _m_lock.unlock();

    OTA_FUNC_LEAVE();
}

int OTAManager::contact_ota_server() {
    OTA_FUNC_ENTER();
    // Contact server and save the update command
    assert(NULL == _m_p_update_command);

    const char* const test_command =  "{" \
                          "\"transaction\": \"123456789\","  \
                          "\"version\": \"1.1.1.2\","  \
                          "\"url\": \"http://10.202.40.47:8084/ota.bin.pack\","  \
                          "\"size\":212203,"  \
                          "\"signature\":\"3E2F80FA\"}";

    int update_command_len = strlen(test_command) + 1;
    _m_p_update_command = (char*)MALLOC(update_command_len, OTA);
    memset(_m_p_update_command, 0, update_command_len);
    memmove(_m_p_update_command, test_command, update_command_len);

    OTA_FUNC_LEAVE();

    return 0;
}

int OTAManager::parse_update_command(const char* str_command,
                                     OTAServerUpdateCommand& command) {
    int parse = 1;

    if (!str_command) {
        DUER_LOGE("Argument invalid");

        return -1;
    }

    OTA_FUNC_ENTER();

    memset(&command, 0, sizeof(command));

    do {
        baidu_json* payload = NULL;

        if (!str_command) {
            DUER_LOGE("Command null pointer");
            break;
        }

        payload = baidu_json_Parse(str_command);

        if (!payload) {
            DUER_LOGE("Invalid command:%s", str_command);
            break;
        }

        baidu_json* child = payload->child;
        child = child ? child->child : NULL;

        while (child) {
            if (0 == strcmp(child->string, "transaction")) {
                memmove(command.transaction, child->valuestring, sizeof(command.transaction) - 1);
                DUER_LOGD("command.transaction=%s", command.transaction);
            } else if (0 == strcmp(child->string, "version")) {
                memmove(command.version, child->valuestring, sizeof(command.version) - 1);
                DUER_LOGD("command.version=%s", command.version);
            } else if (0 == strcmp(child->string, "old_version")) {
                memmove(command.old_version, child->valuestring, sizeof(command.old_version) - 1);
                DUER_LOGD("command.old_version=%s", command.old_version);
            } else if (0 == strcmp(child->string, "url")) {
                memmove(command.url, child->valuestring, sizeof(command.url) - 1);
                DUER_LOGD("command.url=%s", command.url);
            } else if (0 == strcmp(child->string, "size")) {
                command.size = child->valueint;
                DUER_LOGD("command.size=%d", command.size);
            } else if (0 == strcmp(child->string, "signature")) {
                memmove(command.signature, child->valuestring, sizeof(command.signature) - 1);
                DUER_LOGD("command.signature=%s", command.signature);
            } else {
                // DO nothing
            }

            child = child->next;
        }

        parse = 0;
    } while (0);

    OTA_FUNC_LEAVE();

    return parse;
}

void OTAManager::run_installer() {
    void* ctx = NULL;
    int status = 0;
    struct IOtaUpdater updater;
    OTAServerUpdateCommand command;

    OTA_FUNC_ENTER();

#ifdef TARGET_UNO_91H
    rda_ota_update_context update_cxt;
    memset(&update_cxt, 0, sizeof(update_cxt));
    ctx = &update_cxt;
    updater.notify_data_begin = rda_notify_data_begin;
    updater.notify_meta_data = rda_notify_meta_data;
    updater.notify_module_data = rda_notify_module_data;
    updater.notify_data_end = rda_notify_data_end;
    updater.update_img_begin = rda_ota_update_image_begin;
    updater.update_img = rda_ota_update_image;
    updater.update_img_end = rda_ota_update_image_end;
#endif
    parse_update_command(_m_p_update_command, command);
    NewFirmwareImageUnpackTool tool(128, command.size, &updater, ctx, _m_p_reporter);

    DUER_LOGI("Begin unpack");
    status = tool.unpack_ota_package_begin();
    if (status != 0) {
        DUER_LOGE("Unpack OTA package begin fail!! status:%d", status);
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_WRITE_ERROR);
    }

    // Read from shared Ring Buffer
    const int n_buffer_size = SHARED_BUFFER_SIZE;
    unsigned char* ch_buffer = NEW(OTA) unsigned char[n_buffer_size];
    unsigned int n_read_bytes = 0;

    DUER_LOGI("Start to read from buffer");
    n_read_bytes = _m_shared_buffer.read(ch_buffer, n_buffer_size);
    while (n_read_bytes) {
        if (0 == status) {
            status = tool.unpack_ota_package(ch_buffer, n_read_bytes);
        }
        n_read_bytes = _m_shared_buffer.read(ch_buffer, n_buffer_size);
    }

    if (0 == status) {
        status = tool.unpack_ota_package_end();
    }

    if (0 == status) {
        tool.update_image_begin();
        tool.update_image();
        tool.update_image_end();
        // After upgrade image update, do a scan
        _m_scanner.scan();
        // Start a normal priority thread to do restart,
        // Restart will happen, so don't care about the new resource[Thread] release
        _m_p_restart_thread = NEW(OTA) rtos::Thread(osPriorityNormal, WRITE_STACK_SIZE);
        _m_p_restart_thread->start(OTAManager::ota_resart);
    } else {
        DUER_LOGE("OTA event write error");
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_WRITE_ERROR);
    }

    delete[] ch_buffer;
    _m_p_install_complete->release();

    OTA_FUNC_LEAVE();
}

int OTAManager::apply_updates(const OTAServerUpdateCommand& command) {
    int ret = 0;
    URL url(command.url);
    IOTADownloader* p_downloader = NULL;

    OTA_FUNC_ENTER();

    if (!url.valid()) {
        DUER_LOGE("Invalid URL:%s", url.url());
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_REJECT);

        return 1;
    }

    if (0 == strcmp("coaps", url.scheme())) {
        p_downloader = NEW(OTA) OTACoapDownloader(&url, NULL);
        DUER_LOGI("Create OTACoapDownloader");
    } else if (0 == strcmp("http", url.scheme())) {
        p_downloader = NEW(OTA) OTAHttpDownloader(&url);
        DUER_LOGI("Create OTAHttpDownloader");
    } else {
        // Do nothing
    }

    ret = p_downloader->connect();

    if (ret != 0) {
        DUER_LOGE("Connect fail!!!");
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_CONNECT_FAIL);

        return ret;
    }

    _m_p_reporter->report_event(OTAReporter::OTA_EVENT_BEGIN);
    rtos::Thread writer(osPriorityHigh, WRITE_STACK_SIZE);
    _m_p_install_complete = NEW(OTA) rtos::Semaphore(0);
    writer.start(this, &OTAManager::run_installer);
    DUER_LOGI("Installer task id:0x%08x", writer.gettid());
    downloader_cb_ctx ctx = {
        0,
        command.size,
        this,
        0.0,
    };

    // TODO
    while (ctx.nOffset < command.size) {
        ret = p_downloader->get(&OTAManager::data_available, &ctx);
        DUER_LOGI("Download status:%d", ret);

        if (ret != 0) {
            _m_p_reporter->report_event(OTAReporter::OTA_EVENT_CONNECTION_LOST);
            DUER_LOGE("Report connection lost, download status:%d", ret);

            break;
        }

        DUER_LOGI("Download status:%d, ctx.nOffset:%d, command.size:%d",
                    ret, ctx.nOffset, command.size);
    }

    _m_shared_buffer.wait();
    p_downloader->disconnect();
    DUER_LOGI("Waiting for installer complete");
    _m_p_install_complete->wait(osWaitForever);
    DUER_LOGI("Installer complete, completed");
    delete _m_p_install_complete;
    _m_p_install_complete = NULL;

    OTA_FUNC_LEAVE();

    return 0;
}

int OTAManager::data_available(unsigned char* data, unsigned int len, void* ctx) {
    if (!data || !ctx) {
        DUER_LOGE("Argument invalid");

        return -1;
    }

    downloader_cb_ctx* cb_ctx = (downloader_cb_ctx*)ctx;

    if (cb_ctx) {
        while (len > 0) {
            unsigned int write = cb_ctx->p_ota_manager->_m_shared_buffer.write(data, len);
            cb_ctx->nOffset += write;
            data += write;
            len -= write;
        }
    }

    return 0;
}

int OTAManager::sync_package_info() {
    OTA_FUNC_ENTER();

#ifdef TARGET_UNO_91H
    if (0 == _m_scanner.scan()) {
        DUER_LOGI("Package scanner scan successfully");
        struct os_info info;
        _m_scanner.get_os_info(info);
        OTAReporter::report_package_info(info);
    }
#endif

    OTA_FUNC_LEAVE();

    return 0;
}

int OTAManager::get_os_info(struct os_info& info) {
    return _m_scanner.get_os_info(info);
}

void OTAManager::ota_resart() {
    Thread::wait(5000); // sleep 5s before restart
    rda_ota_restart();
}

} // namespace duer
